#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/ioctl.h>
#include <sys/ioctl.h>
#include <sys/select.h>

#include "../../include/public/port/serial.h"
#include "../../include/public/port/serial_private.h"

serial_t *serial_create(const char *device,
                        int baud, char parity, int data_bit,
                        int stop_bit)
{
    if (device == NULL || *device == 0)
    {
        printf("The device string is empty\n");
        return NULL;
    }

    serial_t *serial = (serial_t *)malloc(sizeof(serial_t));
    if (serial == NULL)
    {
        printf("malloc serial error!\n");
        return NULL;
    }
    memset(serial->device, 0, sizeof(serial->device));

    strcpy(serial->device, device);
    serial->baud = baud;
    serial->parity = parity;
    serial->data_bit = data_bit;
    serial->stop_bit = stop_bit;

    serial->msp = (void *)malloc(sizeof(serial_private_t));
    if (serial->msp == NULL)
    {
        printf("malloc serial special param error!\n");
        return NULL;
    }
    serial_private_t *msp = (serial_private_t *)serial->msp;

    msp->onebyte_time = 1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud;
    msp->lastbyte_delay = 10 * 9600 * 1000 / baud;

    return serial;
}

void serial_release(serial_t *serial)
{
    if (serial != NULL)
    {
        if (serial->msp != NULL)
            free(serial->msp);
        free(serial);
    }
}

int serial_open(serial_t *serial)
{
    struct termios tios;
    speed_t speed;
    serial->s = open(serial->device, O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL);
    if (serial->s == -1)
    {
        printf("ERROR Can't open the device %s (%s)\n",
               serial->device, strerror(errno));
        return -1;
    }

    serial_private_t *msp = (serial_private_t *)serial->msp;

    /* Save */
    tcgetattr(serial->s, &(msp->old_tios));

    memset(&tios, 0, sizeof(struct termios));
    switch (serial->baud)
    {
    case 110:
        speed = B110;
        break;
    case 300:
        speed = B300;
        break;
    case 600:
        speed = B600;
        break;
    case 1200:
        speed = B1200;
        break;
    case 2400:
        speed = B2400;
        break;
    case 4800:
        speed = B4800;
        break;
    case 9600:
        speed = B9600;
        break;
    case 19200:
        speed = B19200;
        break;
    case 38400:
        speed = B38400;
        break;
    case 57600:
        speed = B57600;
        break;
    case 115200:
        speed = B115200;
        break;
    default:
        speed = B9600;
        printf("WARNING Unknown baud rate %d for %s (B9600 used)\n",
               serial->baud, serial->device);
    }

    /* Set the baud rate */
    if ((cfsetispeed(&tios, speed) < 0) ||
        (cfsetospeed(&tios, speed) < 0))
    {
        close(serial->s);
        serial->s = -1;
        return -1;
    }
    tios.c_cflag |= (CREAD | CLOCAL);
    tios.c_cflag &= ~CSIZE;
    switch (serial->data_bit)
    {
    case 5:
        tios.c_cflag |= CS5;
        break;
    case 6:
        tios.c_cflag |= CS6;
        break;
    case 7:
        tios.c_cflag |= CS7;
        break;
    case 8:
    default:
        tios.c_cflag |= CS8;
        break;
    }

    /* Stop bit (1 or 2) */
    if (serial->stop_bit == 1)
        tios.c_cflag &= ~CSTOPB;
    else /* 2 */
        tios.c_cflag |= CSTOPB;

    /* PARENB       Enable parity bit
       PARODD       Use odd parity instead of even */
    if (serial->parity == 'N')
    {
        /* None */
        tios.c_cflag &= ~PARENB;
    }
    else if (serial->parity == 'E')
    {
        /* Even */
        tios.c_cflag |= PARENB;
        tios.c_cflag &= ~PARODD;
    }
    else
    {
        /* Odd */
        tios.c_cflag |= PARENB;
        tios.c_cflag |= PARODD;
    }
    tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    if (serial->parity == 'N')
    {
        /* None */
        tios.c_iflag &= ~INPCK;
    }
    else
    {
        tios.c_iflag |= INPCK;
    }
    tios.c_iflag &= ~(IXON | IXOFF | IXANY);
    tios.c_oflag &= ~OPOST;
    tios.c_cc[VMIN] = 0;
    tios.c_cc[VTIME] = 0;

    if (tcsetattr(serial->s, TCSANOW, &tios) < 0)
    {
        close(serial->s);
        serial->s = -1;
        return -1;
    }

    // serial_private_t* msp = (serial_private_t*)serial->msp;

    printf("libserial open %s-%d,%c%d%d OK!\n",
           serial->device, serial->baud, serial->parity, serial->data_bit, serial->stop_bit);

    return 0;
}

void serial_close(serial_t *serial)
{
    serial_private_t *msp = (serial_private_t *)serial->msp;
    tcsetattr(serial->s, TCSANOW, &(msp->old_tios));
    close(serial->s);
}

int serial_write(serial_t *serial, uint8_t *msg, int msg_len)
{
    int size = write(serial->s, msg, msg_len);

    return size;
}

// timeout为-1时,永久阻塞
static int serial_select(serial_t *serial, int timeout)
{
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(serial->s, &rfds);
    struct timeval tv;
    tv.tv_sec = timeout / 1000;
    tv.tv_usec = (timeout % 1000) * 1000;

    struct timeval *time;
    if (timeout == -1)
        time = NULL;
    else
        time = &tv;

    int s_rc;
    while ((s_rc = select(serial->s + 1, &rfds, NULL, NULL, time)) == -1)
    {
        if (errno == EINTR)
        {
            printf("A non blocked signal was caught\n");
            FD_ZERO(&rfds);
            FD_SET(serial->s, &rfds);
        }
        else
        {
            return -1;
        }
    }

    if (s_rc == 0)
    {
        /* Timeout */
        errno = ETIMEDOUT;
        // return -1;
    }

    return s_rc;
}

// 0--超时
// -1--读取失败
// len--返回读到的长度
int serial_read(serial_t *serial, uint8_t *msg, int msg_len, int timeout)
{
    int len = 0;
    int rc = 0;
    int err = 0;
    serial_private_t *msp = (serial_private_t *)serial->msp;

    while (msg_len > 0)
    {
        rc = serial_select(serial, timeout);
        if (rc < 0)
        {
            err = rc;
            // printf("select error!\n");
            break;
        }
        else if (rc == 0)
        {
            // printf("select timeout!\n");
            break;
        }

        rc = read(serial->s, msg + len, msg_len);
        if (rc <= 0)
        {
            if (rc < 0)
            {
                // printf("read error!\n");
                err = rc;
            }
            else
            {
                // printf("read null!\n");
            }
            break;
        }
        len += rc;
        msg_len -= rc;

        // timeout = msp->onebyte_time / 1000 + 10;
        timeout = 50;
        // timeout = msp->lastbyte_delay / 1000;
    }

    if (err < 0)
        return err;
    else
        return len;
}

int serial_flush(serial_t *serial)
{
    return tcflush(serial->s, TCIOFLUSH);
}
